/*
 * ========THE SOLMIX PROJECT=====================================
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.gnu.org/licenses/ 
 * or see the FSF site: http://www.fsf.org. 
 */

package org.solmix.sql;

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.sql.DataSource;

import org.apache.commons.dbcp.PoolableConnection;
import org.apache.commons.pool.ObjectPool;
import org.solmix.api.exception.SLXException;
import org.solmix.api.pool.IPoolableObjectFactory;
import org.solmix.api.pool.SlxPoolableObjectFactory;
import org.solmix.api.types.Texception;
import org.solmix.api.types.Tmodule;
import org.solmix.commons.collections.DataTypeMap;
import org.solmix.commons.logs.Logger;
import org.solmix.commons.util.DataUtil;
import org.solmix.fmk.base.Reflection;
import org.solmix.sql.internal.SQLConfigManager;

/**
 * 
 * @author solomon
 * @version $Id$ 2011-3-20
 */
@SuppressWarnings("unchecked")
public class PoolableSQLConnectionFactory extends SlxPoolableObjectFactory
{

    private static Logger log = new Logger(PoolableSQLConnectionFactory.class.getName());


    private final boolean autoDeriveConfig;

    private EInterfaceType interfaceType;

    private boolean driverManagerRegistered;

    private String serverName;

    private DataTypeMap sqlConfig;

    private DataTypeMap driverConfig;

    private DataTypeMap databaseConfig;

    private String databaseType;

    private String pingTest;

    private DataSource ds;

    public PoolableSQLConnectionFactory()
    {
        autoDeriveConfig = false;
        driverManagerRegistered = false;
        interfaceType = EInterfaceType.DATASOURCE;
    }

    public PoolableSQLConnectionFactory(String serverName) throws SLXException
    {
        this(serverName, SQLConfigManager.getConfig().getSubtree((new StringBuilder()).append("sql.").append(serverName).toString()));
    }

    /**
     * @param serverName
     * @param subtree
     */
    public PoolableSQLConnectionFactory(String serverName, DataTypeMap sqlConfig)
    {
        autoDeriveConfig = false;
        driverManagerRegistered = false;
        interfaceType = EInterfaceType.DATASOURCE;
        this.serverName = serverName;
        this.sqlConfig = sqlConfig;
        EInterfaceType _interfaceType = EInterfaceType.fromValue(sqlConfig.getString("interface.type"));
        if (_interfaceType == null)
            log.warning((new StringBuilder()).append("sql.").append(serverName).append(".interface.type not set - assuming ").append(
                this.interfaceType).toString());
        else
            this.interfaceType = _interfaceType;
        driverConfig = sqlConfig.getSubtree("driver");
        databaseConfig = sqlConfig.getSubtree("database");
        if (!sqlConfig.getBoolean("autoDeriveConfig", false)) {
            databaseType = databaseConfig.getString("type");
            pingTest = sqlConfig.getString((new StringBuilder()).append(databaseType).append(".pingTest").toString());
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.solmix.api.pool.SlxPoolableObjectFactory#makeUnpooledObject()
     */
    @Override
    public Object makeUnpooledObject() throws Exception {
        Lock l = new ReentrantLock();
        switch (interfaceType) {
            case DATASOURCE: {
                l.lock();
                try {
                    if (ds == null) {
                        String impl = sqlConfig.getString("driver");
                        if (log.isDebugEnabled())
                            log.debug("Initializing sql config for [" + serverName + "] from system config -using DataSource: " + impl);
                        ds = (DataSource) Reflection.newInstance(impl);
                        DataUtil.setProperties(driverConfig, ds);
                        if (ds != null && sqlConfig.getBoolean("log.enabled", false))
                            ds.setLogWriter(new PrintWriter(System.out));
                    }
                    if (ds == null) {
                        String __info = "Unable to instantiate JDBC DataSource for database [" + serverName + "] - check your config.";
                        throw new SLXException(Tmodule.POOL, Texception.CAN_NOT_INSTANCE, __info);
                    } else {
                        return ds.getConnection();
                    }
                } finally {
                    l.unlock();
                }

            }
            case DRIVERMANAGER: {
                String jdbcURL = driverConfig.getString("url");
                Boolean credentialsSetting = sqlConfig.getBoolean("interface.credentialsInURL");
                boolean credentialsInURL = credentialsSetting == null || credentialsSetting.booleanValue();
                if (jdbcURL == null)
                    jdbcURL = (new StringBuilder()).append("jdbc:").append(driverConfig.getString("driverName")).append("://").append(
                        driverConfig.getString("serverName")).append(":").append(driverConfig.get("portNumber")).append("/").append(
                        driverConfig.get("databaseName")).append(
                        credentialsInURL ? (new StringBuilder()).append("?user=").append(driverConfig.get("user")).append("&password=").append(
                            driverConfig.get("password")).toString() : "").toString();
                Class driver = null;
                l.lock();
                try {
                    if (!driverManagerRegistered) {
                        String dmImplementer = sqlConfig.getString("driver");
                        log.debug((new StringBuilder()).append("Initializing SQL config for '").append(serverName).append("' from system config").append(
                            " - using DriverManager:  ").append(dmImplementer).toString());
                        driver = Reflection.classForName(dmImplementer);
                        if (driver != null) {
                            log.debug((new StringBuilder()).append(dmImplementer).append(" lookup successful").toString());
                            driverManagerRegistered = true;
                        } else {
                            throw new Exception(
                                (new StringBuilder()).append("Unable to register JDBC DriverManager for database ").append(serverName).append(
                                    " - check your config.").toString());
                        }
                    }
                } finally {
                    l.unlock();
                }
                log.debug((new StringBuilder()).append("DriverManager fetching connection for ").append(serverName).append(" via jdbc url ").append(
                    jdbcURL).toString());
                if (!credentialsInURL) {
                    log.debug("Passing credentials getConnection separately from JDBC URL");
                    return DriverManager.getConnection(jdbcURL, driverConfig.getString("user"), driverConfig.getString("password"));
                } else {
                    log.debug("Passing JDBC URL only to getConnection");
                    return DriverManager.getConnection(jdbcURL);
                }
            }
            case JNDI: {
                // TODO
            }
            case JNDIOSGI: {
                // TODO
            }
            case OSGI: {
                // TODO
            }
            default: {
                throw new SLXException(Tmodule.POOL, Texception.NO_SUPPORT, "Unsupported interface type " + interfaceType + " for database "
                    + serverName + " - check your config.");
            }
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.apache.commons.pool.PoolableObjectFactory#activateObject(java.lang.Object)
     */
    @Override
    public void activateObject(Object obj) throws Exception {
        numActivateObjectCalls++;
        try {
            if (obj instanceof PoolableConnection)
                ((PoolableConnection) obj).reallyClose();
            else
                ((Connection) obj).close();
        } catch (Throwable ignored) {
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.apache.commons.pool.PoolableObjectFactory#destroyObject(java.lang.Object)
     */
    @Override
    public void destroyObject(Object arg0) throws Exception {
        numDestroyObjectCalls++;

    }

    /**
     * {@inheritDoc}
     * 
     * @see org.apache.commons.pool.PoolableObjectFactory#makeObject()
     */
    @Override
    public Object makeObject() throws Exception {
        numMakeObjectCalls++;
        Connection conn = (Connection) makeUnpooledObject();
        if (pool == null) {
            log.debug("Returning unpooled Connection");
            return conn;
        } else {
            log.debug("Returning pooled Connection");
            return new PoolableConnection(conn, (ObjectPool) pool);
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.apache.commons.pool.PoolableObjectFactory#passivateObject(java.lang.Object)
     */
    @Override
    public void passivateObject(Object arg0) throws Exception {
        numPassivateObjectCalls++;

    }

    /**
     * {@inheritDoc}
     * 
     * @see org.apache.commons.pool.PoolableObjectFactory#validateObject(java.lang.Object)
     */
    @Override
    public boolean validateObject(Object obj) {
        Connection conn = (Connection) obj;
        boolean isClosed = false;
        try {
            isClosed = conn.isClosed();
        } catch (SQLException e1) {
            e1.printStackTrace();
        }
        if (isClosed)
            return false;
        try {
            if (pingTest != null) {
                Statement s = conn.createStatement();
                s.execute(pingTest);
            }
            return true;
        } catch (SQLException e) {
        }
        try {
            conn.close();
        } catch (Exception ignored) {
        }
        return false;
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.solmix.api.pool.IPoolableObjectFactory#newInstance(java.lang.Object)
     */
    @Override
    public IPoolableObjectFactory newInstance(Object key) throws SLXException {
        return new PoolableSQLConnectionFactory(key.toString());
    }

}
